<script setup>
import { Plus, UploadFilled, VideoCamera, Camera } from '@element-plus/icons-vue'
import { videoPostEndService, videoPostPartService, videoPostService, videoPostStartService } from '@/api/video';
import { videoTagGetSimpleService } from '@/api/video-tag';
import { ref, watch } from 'vue'

/* 表单 */
const publishFormModel = ref({
  description: '',
  addedTagList: new Set(),
  addedNewTagList: new Set(),
})

const publishFormFileModel = ref({
  cover: null,
  video: null
})

/* 视频 */
const videoUrl = ref('');
const videoElement = ref(null);
const showFrameCapture = ref(false);
const frameCaptureCanvas = ref(null);

const totalChunks = ref(0);
const uploadedChunks = ref(0);
const CHUNK_SIZE = 50 * 1024;
let uploadId = -1; // 上传id
let uploadedChunksMap = 0; // 记录已经上传的分块
const isFailUpload = ref(false); // 是否上传失败

const onVideoFileChange = async (file) => {
  const rawFile = file.raw;

  publishFormFileModel.value.video = rawFile;
  videoUrl.value = URL.createObjectURL(rawFile);

  // 初始化值
  uploadedChunksMap = 0;

  // 计算总分块数
  totalChunks.value = Math.ceil(rawFile.size / CHUNK_SIZE);
  uploadedChunks.value = 0;

  // 发起上传
  uploadId = (await videoPostStartService(rawFile.size, totalChunks.value)).data.data;

  chunkedUpload(rawFile);
}

const chunkedUpload = async (rawFile) => {

  // 循环上传分块
  while (uploadedChunks.value < totalChunks.value) {
    for (let chunkNumber = 0; chunkNumber < totalChunks.value; chunkNumber++) {
      if ((uploadedChunksMap & (1 << chunkNumber)) != 0) { // 代表已经上传
        continue;
      }
      const start = chunkNumber * CHUNK_SIZE;
      const end = Math.min(rawFile.size, start + CHUNK_SIZE); // 防止最后一个分块超出文件大小
      let failCnt = 0; // 记录上传该分块失败的次数

      // 使用 slice 方法切割文件
      const chunk = rawFile.slice(start, end);
    
      while (true) {
        try {
          const code = (await videoPostPartService(uploadId, chunkNumber, chunk)).data.code;
          if (code) {
            uploadedChunks.value++; // 增加已上传分块数量
            uploadedChunksMap |= 1 << chunkNumber; // 记录该分块已上传
            await new Promise(r => setTimeout(r, 1000));
            break;
          } else {
            failCnt++;

          }
        } catch(err) {
          failCnt++;
        } finally {
          if (failCnt >= 3) { // 上传当前分块失败次数过多，则暂停上传。
            isFailUpload.value = true;
            return;
          }
          await new Promise(r => setTimeout(r, 1000));
        }
      }
    }
  }
  if (uploadedChunks.value == totalChunks.value) { // 其实执行到这里就已经代表上传完毕
    videoPostEndService(uploadId);
  }
}

/* 标签 */
const tags = ref([]);
const newTagName = ref('');

const onClickGetTags = async() => {
  const res = await videoTagGetSimpleService();
  tags.value = res.data.data;
}

const onClickAddTag = (tag) => {
  publishFormModel.value.addedTagList.add(tag);
}

const onCloseTag = (tag) => {
  publishFormModel.value.addedTagList.delete(tag);
}

const addNewTag = () => {
  if (newTagName.value.trim()) {
    const newTag = {
      id: Date.now(),
      name: newTagName.value.trim()
    };
    publishFormModel.value.addedNewTagList.add(newTag);
    newTagName.value = '';
  }
}

/* 封面 */
const coverUrl = ref('');
const allowedAvatarFileTypes = 'image/png, image/jpeg';

const onCoverFileChange = (file) => {
  publishFormFileModel.value.cover = file.raw;
  coverUrl.value = URL.createObjectURL(file.raw);
}

/* 视频帧捕获 */
const captureFrame = () => {
  if (!videoElement.value || !frameCaptureCanvas.value) return;
  
  const video = videoElement.value;
  const canvas = frameCaptureCanvas.value;
  const context = canvas.getContext('2d');
  
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  context.drawImage(video, 0, 0, canvas.width, canvas.height);
  
  canvas.toBlob(blob => {
    const file = new File([blob], "cover.png", { type: "image/png" });
    publishFormFileModel.value.cover = file;
    coverUrl.value = URL.createObjectURL(blob);
    showFrameCapture.value = false;
  }, 'image/png');
}

/* 发布 */
const publish = async() => {
  if (!publishFormFileModel.value.cover) {
    ElMessage.warning('请设置视频封面');
    return;
  }
  
  const publishFormData = JSON.parse(JSON.stringify(publishFormModel.value));
  // 格式转换
  const addedTagList = [];
  const addedNewTagList = [];
  for (const tag of publishFormModel.value.addedTagList) {
    addedTagList.push(tag.id);
  }
  for (const tag of publishFormModel.value.addedNewTagList) {
    addedNewTagList.push(tag.id);
  }
  publishFormData.addedTagList = addedTagList;
  publishFormData.addedNewTagList = addedNewTagList;

  try {
    const res = await videoPostService(publishFormData, publishFormFileModel.value);
    ElMessage.success('视频发布成功');
  } catch (error) {
    ElMessage.error('视频发布失败');
  }
};

// 监听视频URL变化
watch(videoUrl, (newUrl) => {
  if (newUrl) {
    // 自动从视频中捕获第一帧作为封面
    setTimeout(() => {
      if (videoElement.value) {
        videoElement.value.currentTime = 0.5;
      }
    }, 500);
  }
});
</script>

<template>
  <el-container class="container">
    <el-container class="container-middle">
      <el-form>
        <el-form-item label="视频简介">
          <el-input 
            v-model="publishFormModel.description" 
            placeholder="添加视频简介" 
            type="textarea" 
            maxlength="256" 
            show-word-limit 
            :autosize="{ minRows: 4, maxRows: 8 }"
          />
        </el-form-item>

        <el-form-item label="视频标签">
          <el-popover placement="right" :width="400" trigger="click">
            <template #reference>
              <div>
                <el-tag class="added-tag" v-for="tag in publishFormModel.addedTagList" :key="tag.id" type="danger" effect="dark" @close="onCloseTag(tag)" closable>
                  {{ tag.name }}
                </el-tag>
                <el-button @click="onClickGetTags" :icon="Plus">添加视频标签</el-button>
              </div>
            </template>
            <div class="video-tag-container">
              <div class="video-tag-container__header">
                标签
              </div>
              <div class="video-tag-container__main">
                <el-tag class="tag" v-for="tag in tags" :key="tag.id" type="primary" effect="dark" @click="onClickAddTag(tag)">
                  {{ tag.name }}
                </el-tag>
                <el-input></el-input>
              </div>
            </div>
          </el-popover>
        </el-form-item>
        
        <el-form-item label="视频封面">
          <div class="cover-upload-container">
            <el-upload
              class="video-cover-container"
              :auto-upload="false"
              :show-file-list="false"
              :accept="allowedAvatarFileTypes"
              :on-change="onCoverFileChange"
            >
              <img v-if="coverUrl" :src="coverUrl" class="avatar" />
              <el-icon v-else class="video-uploader-icon"><Camera /></el-icon>
            </el-upload>
            
            <el-button 
              v-if="videoUrl"
              type="primary" 
              :icon="VideoCamera"
              @click="showFrameCapture = true"
              class="frame-capture-btn"
            >
              从视频截取
            </el-button>
          </div>
        </el-form-item>

        <el-form-item class="publish-button-container">
          <el-button 
            class="publish-button" 
            type="primary" 
            @click="publish"
            :disabled="!videoUrl"
          >
            发布视频
          </el-button>
        </el-form-item>
      </el-form>
    </el-container>
    
    <el-aside class="container-right-aside">
      <div class="preview-container">
        <div class="phone-mockup">
          <div class="preview-video-container">
            <video 
              ref="videoElement"
              class="preview-video" 
              :src="videoUrl" 
              :poster="coverUrl"
              autoplay
              loop
            ></video>
          </div>
        </div>
        <el-progress
          style="width: 80%; "
          :percentage="Math.ceil((totalChunks ? uploadedChunks / totalChunks : 0) * 100)"
          striped
          :status="uploadedChunks / totalChunks == 1 ? 'success' : null"
          striped-flow
        />
        <el-button v-show="isFailUpload" type="danger" @click="chunkedUpload(publishFormFileModel.video); isFailUpload = false">重新上传</el-button>
        <div class="upload-container">
          <el-upload
            :auto-upload="false"
            :show-file-list="false"
            :on-change="onVideoFileChange"
            drag
          >
            <el-icon class="el-icon--upload"><upload-filled /></el-icon>
            <div class="el-upload__text">
              点击上传<em>或直接将视频文件拖入此区域</em>
              <div class="el-upload__hint">支持 MP4、MOV 格式，最大 2GB</div>
            </div>
          </el-upload>
        </div>
      </div>
    </el-aside>
    
    <!-- 视频帧捕获对话框 -->
    <el-dialog v-model="showFrameCapture" title="选择视频封面" width="800px">
      <div class="frame-capture-dialog">
        <video 
          ref="videoElement"
          class="capture-video" 
          :src="videoUrl" 
          autoplay
          muted
          controls
        ></video>
        
        <div class="capture-controls">
          <el-button type="primary" @click="captureFrame">
            <el-icon><Camera /></el-icon> 使用当前帧
          </el-button>
        </div>
        
        <canvas ref="frameCaptureCanvas" style="display: none;"></canvas>
      </div>
    </el-dialog>
  </el-container>
</template>

<style lang="scss" scoped>
.container {
  width: 100%;
  max-width: 100%;
  height: 100%;
  max-height: 100%;
  padding: 2%;
  display: flex;
  gap: 30px;
}

.container-middle {
  box-sizing: border-box;
  width: 50%;
  height: 100%;
  padding: 2% 5%;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
  display: flex;
  flex-direction: column;
  justify-content: center;

  .el-form {
    display: flex;
    flex-direction: column;
    gap: 30px;
  }

  ::v-deep(.el-form-item__label) {
    font-weight: 600;
    color: #333;
    font-size: 15px;
  }
}

.container-right-aside {
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 45%;

  .preview-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100%;
    gap: 30px;
    
    .phone-mockup {
      position: relative;
      width: 300px;
      flex: 1;
      background: url('/src/assets/creator/phone.png') center/contain no-repeat;
      padding: 15px;
      
      .preview-video-container {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 70%;
        height: 76%;
        border-radius: 8px;
        overflow: hidden;

        .preview-video {
          width: 100%;
          height: 100%;
          object-fit: contain;
        }
      }
    }
    
    .upload-container {
      width: 100%;
      max-width: 500px;
    }
  }
}

.tag-container {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 12px;
  
  .el-tag {
    cursor: pointer;
    transition: all 0.3s;
    
    &:hover {
      transform: translateY(-2px);
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
    }
  }
}

.video-tag-container {
  display: flex;
  flex-direction: column;
  max-height: 400px;
  overflow-y: auto;

  &__header {
    padding: 8px 0;
    font-weight: 600;
    text-align: center;
    border-bottom: 1px solid #eee;
    font-size: 16px;
    color: #333;
    margin-bottom: 12px;
  }

  &__main {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    margin-bottom: 20px;

    .tag {
      cursor: pointer;
      transition: all 0.2s;
      margin: 4px;
      
      &:hover {
        transform: scale(1.05);
      }
    }
  }
}

.new-tag-section {
  padding-top: 15px;
  border-top: 1px dashed #eee;
}

.cover-upload-container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 150px;
  height: 150px;
  
  .video-cover-container {
    width: 100%;
    height: 100%;
    min-width: 100%;
    min-height: 100%;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    transition: border-color 0.3s;
    display: flex;
    justify-content: center;
    align-items: center;
    
    :deep(.el-upload) {
      width: 100%;
      height: 100%;
    }
    
    &:hover {
      border-color: #409eff;
    }

    .avatar {
      width: 150px;
      height: 150px;
      object-fit: cover;
    }
  }
  
  .frame-capture-btn {
    margin-left: 5px;
    height: 40px;
  }
}

.el-icon.video-uploader-icon {
  font-size: 28px;
  color: #8c939d;
}

.publish-button-container {
  margin-top: 30px;
  ::v-deep(.el-form-item__content) {
    justify-content: center;
  }

  .publish-button {
    width: 100%;
    height: 45px;
    font-size: 16px;
    font-weight: 600;
    letter-spacing: 1px;
  }
}

.frame-capture-dialog {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  
  .capture-video {
    width: 100%;
    max-height: 400px;
    border-radius: 8px;
    background: #000;
  }
  
  .capture-controls {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 15px;
  }
}

.el-upload__hint {
  font-size: 12px;
  color: #999;
  margin-top: 8px;
}
</style>